<template>
    <div class="v-canvas-grid" id="canvasDiv">
        <template v-if="suportWebGL">
            <canvas ref="canvas" class="canvas" width="100" height="100"></canvas>
            <slot></slot>
            <canvas ref="canvasTop" class="canvasTop" width="100" height="100"></canvas>
        </template>
        <div v-else>
            <slot>
                Your browser does not seem to support
                <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">WebGL</a
                >.<br />'
            </slot>
        </div>
    </div>
</template>

<script>
import _ from 'lodash';
import bus from './Eventbus.js';
import Point from './Point.js';
import Hammer from 'hammerjs';
import * as Matrix from './Matrix.js';
import * as Painter from '../core/Painter.js';
import * as utils from './utils.js';

/**
 * v-canvas-grid
 */
const suportWebGL = (() => {
    try {
        const canvas = document.createElement('canvas');
        return !!(
            window.WebGLRenderingContext &&
            (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))
        );
    } catch (e) {
        return false;
    }
})();
const RULER_TYPE = {
    HORIZONTAL: 1,
    VERTICAL: 2,
    ALL: 3,
    NONE: 0
};
export default {
    name: 'v-canvas-grid',
    props: {
        // display mode of the map, can be '2d' or 'isometric', default is '', means '2d', maybe '3d' in the future.
        mode: {
            type: String,
            default: ''
        },
        map: Object, // map arguments
        // coordinates scope, same as dimensions: [minX, maxX, minY, maxY]
        coordinateScope: Array,
        // scaling scope: [min, max]
        scalingScope: Array,
        dimensions: {
            type: Array,
            default: () => [-100000, +100000, -100000, +100000]
        },
        // pointer events
        clickFunc: Function,
        pointerDownFunc: Function,
        pointerUpFunc: Function,
        pointerLeaveFunc: Function,
        pointerMoveFunc: Function,
        // hammer events
        tapFunc: Function,
        panFunc: Function,

        showCompass: Boolean,
        showPanic: Boolean,
        scalingable: {
            type: Boolean,
            default: true
        },
        draggable: {
            type: Boolean,
            default: true
        },
        multiSelectable: Boolean,
        disableGrid: {
            type: Boolean,
            default: false
        },
        disableRuler: {
            type: Boolean,
            default: false
        },
        rulerMode: Number
    },
    data() {
        return {
            currentMap: {
                width: 0,
                height: 0,
                minX: 0,
                maxX: 0,
                minY: 0,
                maxY: 0,
                originX: 0,
                originY: 0,
                scaling: 1,
                grid: {
                    id: 'mapGrid',
                    width: 100,
                    height: 100,
                    x: 0,
                    y: 0,
                    patternUnits: 'userSpaceOnUse',
                    spacing: '',
                    spacingUnit: ''
                },
                // gridInfo: '',
                subGrid: {
                    id: 'mapSubGrid',
                    width: 10,
                    height: 10,
                    patternUnits: 'userSpaceOnUse',
                    spacing: '',
                    spacingUnit: ''
                },
                pointer: { x: 0, y: 0, mx: 0, my: 0 }
            },
            trueName: this.$options.name,
            suportWebGL,
            ctx: null,
            ctxTop: null,
            ratio: null,
            webgl: null,
            origin: {
                width: 20,
                height: 20,
                stroke: 'rgba(0,0,0,0.25)',
                strokeWidth: 1,
                fill: 'none',
                transform: 'translate(0 0) rotate(45 10 10)'
            },
            // map: { width: 0, height: 0, minX: 0, maxX: 0, minY: 0, maxY: 0, originX: 0, originY: 0, scaling: 1 },
            shadowFilter: {},
            matrix: null,
            pointerEvents: [],
            delta: 0
        };
    },
    computed: {
        // rotation () { return this.svgMatrix ? -SVG.getRotation(this.svgMatrix) : 0; }
        isometric() {
            return this.mode === 'isometric';
        }
    },
    watch: {
        map(val) {
            this.currentMap = val;
        },
        currentMap: {
            handler(val) {
                this.$emit('mapChange', val);
            },
            deep: true
        },
        // dimensions: { handler () { this.init(); }, deep: true },
        dimensions: {
            handler() {
                console.log('v-canvas-grid.watch.dimensions: ', this.dimensions);
                this.init();
            },
            deep: true
        }
        // svgMatrix: { handler (val) { this.$emit('handleSvgMatrix', val); }, deep: true }
        // rotation (val) { this.$emit('handleRotation', val); }refreshTop
    },
    created() {
        bus.on(`${this.trueName}.refresh`, this.refresh);
        bus.on(`${this.trueName}.zoomIn`, this.zoomIn);
        bus.on(`${this.trueName}.zoomOut`, this.zoomOut);
        bus.on(`${this.trueName}.rotate`, this.rotate);
        bus.on(`${this.trueName}.mousewheel`, this.mouseWheelHandler);
        bus.on(`${this.trueName}.pointerDownHandler`, this.pointerDownHandler);
        bus.on(`${this.trueName}.pointerUpHandler`, this.pointerUpHandler);
        bus.on(`${this.trueName}.pointerLeaveHandler`, this.pointerLeaveHandler);
        bus.on(`${this.trueName}.pointerMoveHandler`, this.pointerMoveHandler);
    },
    unmounted: function() {
        // 清除事件监听
        bus.off(`${this.trueName}.refresh`, this.refresh);
        bus.off(`${this.trueName}.zoomIn`, this.zoomIn);
        bus.off(`${this.trueName}.zoomOut`, this.zoomOut);
        bus.off(`${this.trueName}.rotate`, this.rotate);
        bus.off(`${this.trueName}.mousewheel`, this.mouseWheelHandler);
        bus.off(`${this.trueName}.pointerDownHandler`, this.pointerDownHandler);
        bus.off(`${this.trueName}.pointerUpHandler`, this.pointerUpHandler);
        bus.off(`${this.trueName}.pointerLeaveHandler`, this.pointerLeaveHandler);
        bus.off(`${this.trueName}.pointerMoveHandler`, this.pointerMoveHandler);
        this.unbindEvents();
    },
    mounted() {
        document.getElementById("canvasDiv").oncontextmenu = function (e) {
            return false;
        }
        this.init();
    },
    methods: {
        init() {
            // if (!this.dimensions || !this.dimensions.length || !this.dimensions[0]) return;
            let dimensions = this.dimensions;
            // if (!dimensions || !dimensions.length || !dimensions[0]) dimensions = [-100000, +100000, -100000, +100000];
            if (!dimensions || !dimensions.length || typeof dimensions[0] === 'undefined')
                dimensions = [-100000, +100000, -100000, +100000];

            // 防止Matrix.create函数找不到DOM节点报错-- mod: ouyang on 2020.7.15
            // if (!this.$refs.canvas) return;
            this.matrix = Matrix.create(this.$refs.canvas, dimensions);

            if (!this.ctx) this.ctx = this.$refs.canvas.getContext('2d');
            if (!this.ctxTop) this.ctxTop = this.$refs.canvasTop.getContext('2d');
            if (!this.ratio) this.ratio = Matrix.getPixelRatio(this.ctx);
            // if (!this.ratio) this.ratio = 1;
            this.matrix = Matrix.create(this.$refs.canvas, dimensions, 20, this.ratio);
            console.log('v-canvas-grid.init: ', this.ratio, dimensions);
            // this.initCanvas();
            // this.refreshGrid();
            this.bindEvents();
            this.refresh();
        },
        initCanvas() {
            // console.log('p-painter-grid.initCanvas: ', this._uid, this.currentMap.width, this.currentMap.height);
            let obj = this.$refs.canvas,
                objTop = this.$refs.canvasTop;
            if (!this.ctx) this.ctx = obj.getContext('2d');
            if (!this.ctxTop) this.ctxTop = objTop.getContext('2d');
            if (!this.ratio) this.ratio = Matrix.getPixelRatio(this.ctx);
            // if (!this.ratio) this.ratio = 1;
            let w = parseInt(getComputedStyle(obj.parentNode).width || 100, 10) * this.ratio,
                h = parseInt(getComputedStyle(obj.parentNode).height || 100, 10) * this.ratio,
                mw = window.document.body.offsetWidth * this.ratio,
                mh = window.document.body.offsetHeight * this.ratio;
            w = Math.min(w, mw);
            h = Math.min(h, mh);
            if (this.currentMap && this.currentMap.width === w && this.currentMap.height === h) return;
            this.currentMap.width = w;
            this.currentMap.height = h;
            obj.setAttribute('width', w);
            obj.setAttribute('height', h);
            objTop.setAttribute('width', w);
            objTop.setAttribute('height', h);
        },
        initMap() {
            // let p = new DOMPoint();
            // p = p.matrixTransform(this.matrix);
            // let p = Point.getInstanceByAnotherMatrix(this.matrix, null, false);
            let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
                p = svg.createSVGPoint();
            p = p.matrixTransform(this.matrix);

            this.currentMap.matrix = this.matrix;
            this.currentMap.originX = p.x;
            this.currentMap.originY = p.y;
            // console.log(`v-canvas-grid.refreshGrid: `, this.matrix.toString(), p.x, p.y);

            let gridSpacing = this.getGridSpacing();
            this.currentMap.grid.spacing = gridSpacing <= 1000 ? gridSpacing : gridSpacing / 1000;
            this.currentMap.grid.spacingUnit = gridSpacing <= 1000 ? 'mm' : 'm';
            let scaling = Matrix.getScaling(this.matrix).x,
                rotation = Matrix.getRotation(this.matrix);
            this.currentMap.scaling = scaling;
            this.currentMap.rotation = rotation;
            gridSpacing *= scaling;
            // because grid won't show in firefox if values are greater than 32767
            // p.x %= gridSpacing;
            // p.y %= gridSpacing;

            this.currentMap.grid.x = String(p.x % gridSpacing);
            this.currentMap.grid.y = String(p.y % gridSpacing);
            this.currentMap.grid.offsetX = p.x % gridSpacing;
            this.currentMap.grid.offsetY = p.y % gridSpacing;
            this.currentMap.grid.width = gridSpacing;
            this.currentMap.grid.height = gridSpacing;

            this.currentMap.subGrid.width = String(gridSpacing / 5);
            this.currentMap.subGrid.height = String(gridSpacing / 5);

            this.currentMap.minX = -this.currentMap.originX / this.currentMap.scaling;
            this.currentMap.maxX = (this.currentMap.width - this.currentMap.originX) / this.currentMap.scaling;
            this.currentMap.minY = -(this.currentMap.height - this.currentMap.originY) / this.currentMap.scaling;
            this.currentMap.maxY = this.currentMap.originY / this.currentMap.scaling;
        },
        bindEvents() {
            console.warn('v-canvas-grid.bindEvents: ');
            if (!this.$el) return;
            this.unbindEvents();
            // this.$el.addEventListener('click', this.clickHandler);
            this.$el.addEventListener('mousewheel', this.mouseWheelHandler);
            this.$el.addEventListener('onmousewheel', this.mouseWheelHandler);
            this.$el.addEventListener('DOMMouseScroll', this.mouseWheelHandler);
            /** hammerjs */
            this.hammer = new Hammer(this.$el);
            this.hammer.on('tap', this.handleTap);
            if (utils.isMobile()) {
                this.hammer.on('pan', this.handlePan);
                this.hammer.on('pinch', this.handlePinch);
                this.hammer.get('pinch').set({ enable: true });
            } else if (window.PointerEvent) {
                this.$el.addEventListener('pointerdown', this.pointerDownHandler);
                this.$el.addEventListener('pointerup', this.pointerUpHandler);
                this.$el.addEventListener('pointermove', this.pointerMoveHandler);
                this.$el.addEventListener('pointerleave', this.pointerLeaveHandler);
            } else {
                this.$el.addEventListener('touchstart', this.pointerDownHandler);
                this.$el.addEventListener('touchend', this.pointerUpHandler);
                this.$el.addEventListener('touchmove', this.pointerMoveHandler);
                this.$el.addEventListener('touchcancel', this.pointerLeaveHandler);
                this.$el.addEventListener('mousedown', this.pointerDownHandler);
                this.$el.addEventListener('mouseup', this.pointerUpHandler);
                this.$el.addEventListener('mousemove', this.pointerMoveHandler);
                this.$el.addEventListener('mouseleave', this.pointerLeaveHandler);
            }
        },
        unbindEvents() {
            console.warn('v-canvas-grid.unbindEvents: ');
            if (!this.$el) return;
            // this.$el.removeEventListener('click', this.clickHandler);
            this.$el.removeEventListener('touchstart', this.pointerDownHandler);
            this.$el.removeEventListener('touchend', this.pointerUpHandler);
            this.$el.removeEventListener('touchmove', this.pointerMoveHandler);
            this.$el.removeEventListener('touchcancel', this.pointerLeaveHandler);
            this.$el.removeEventListener('mousedown', this.pointerDownHandler);
            this.$el.removeEventListener('mouseup', this.pointerUpHandler);
            this.$el.removeEventListener('mousemove', this.pointerMoveHandler);
            this.$el.removeEventListener('mouseleave', this.pointerLeaveHandler);
            this.$el.removeEventListener('pointerdown', this.pointerDownHandler);
            this.$el.removeEventListener('pointerup', this.pointerUpHandler);
            this.$el.removeEventListener('pointermove', this.pointerMoveHandler);
            this.$el.removeEventListener('pointerleave', this.pointerLeaveHandler);

            this.$el.removeEventListener('mousewheel', this.mouseWheelHandler);
            this.$el.removeEventListener('onmousewheel', this.mouseWheelHandler);
            this.$el.removeEventListener('DOMMouseScroll', this.mouseWheelHandler);

            /** hammerjs */
            if (this.hammer) {
                this.hammer.off('tap');
                this.hammer.off('pan');
                this.hammer.off('pinch');
            }
        },

        refresh(updatedTag) {
            if (this.refreshing || !this.ctx) return;
            // if (!this.matrix) return setTimeout(this.refresh, 200);
            if (!this.matrix) return this.init();
            // console.log('v-canvas-grid.refresh: ');
            this.refreshing = true;

            this.initCanvas();
            this.initMap();

            // Isometric logic. 
            let cp = new Point(this.map.originX, this.map.originY);
            if (this.isometric) cp = Point.getIsometricPos(cp.x, cp.y);

            // Offscreen Render. 
            let key = `${this.map.width}_${this.map.height}_${cp.x}_${cp.y}_${this.currentMap.scaling}_${this.currentMap.rotation}_${this.rulerMode}`,
                img = this.getCtxCache(key),
                imgTop = this.getCtxTopCache(key);
            // if (updatedTag) this.setCtxTopCache(key, null);
            // if (this.localList.length) updatedTag = true;
            // if no move && no updates, then is't refresh after some times.
            // if (imgTop && !updatedTag && this.delayRenderNum > 10) {
            //     this.dynamicRender();
            //     return (this.refreshing = false);
            // }
            if (!img || updatedTag) img = this.offscreenRender(key);
            if (!imgTop || updatedTag) imgTop = this.offscreenRenderTop(key);
            // if (!imgTop || updatedTag) {
            //     this.delayRenderNum = 0;
            //     imgTop = this.offscreenRenderTop(key);
            // }
            if (this.$refs.canvas.width && this.$refs.canvas.height) {
                this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
                this.ctx.drawImage(img, 0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
                // console.log('-------> ', this.delayRenderNum);
                this.ctxTop.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
                // if (!this.$refs.canvas || !this.$refs.canvas.width) return (this.refreshing = false);
                this.ctxTop.drawImage(imgTop, 0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            }
            // this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            // this.ctx.drawImage(img, 0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            // // console.log('-------> ', this.delayRenderNum);
            // this.ctxTop.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            // // if (!this.$refs.canvas || !this.$refs.canvas.width) return (this.refreshing = false);
            // this.ctxTop.drawImage(imgTop, 0, 0, this.$refs.canvas.width, this.$refs.canvas.height);

            // dynamic effect
            this.dynamicRender();
            this.delayRenderNum++; // means refresh will continue for a while. 

            this.refreshing = false;
        },
        offscreenRender(key) {
            let c = document.createElement('canvas');
            c.setAttribute('width', this.$refs.canvas.width);
            c.setAttribute('height', this.$refs.canvas.height);
            let ctx = c.getContext('2d');

            this.refreshGrid(ctx);
            this.setCtxCache(key, c);

            ctx.restore();
            return c;
        },
        offscreenRenderTop(key) {
            let c = document.createElement('canvas');
            c.setAttribute('width', this.$refs.canvas.width);
            c.setAttribute('height', this.$refs.canvas.height);
            let ctx = c.getContext('2d');

            this.refreshTop(ctx);

            // set canvas cache.
            this.setCtxTopCache(key, c);

            ctx.restore();
            return c;
        },
        dynamicRender() {
            if (this.rulerMode === 1 && !this.disableRuler) {
                Painter.drawRulers(this.ctxTop, this.currentMap.pointer, 25 * this.ratio, this.ratio);
                let p = new Point(this.currentMap.originX, this.currentMap.originY);
                Painter.drawCentralLine(this.ctxTop, p.x, p.y, this.$refs.canvas.width, this.$refs.canvas.height);
            }
            if (this.showPanic) Painter.drawPanic(this.ctxTop, 50 * this.ratio, 50 * this.ratio, 12 * this.ratio);
            if (this.multiSelector && Object.keys(this.multiSelector).length) {
                // console.log('v-canvas-grid.dynamicRender: ', this.multiSelector);
                this.ctxTop.save();
                this.ctxTop.lineWidth = 10.5;
                this.ctxTop.strokeStyle = 'rgba(0, 0, 0, 0.5)';
                Painter.drawSelectRect(
                    this.ctxTop,
                    this.multiSelector.x,
                    this.multiSelector.y,
                    this.multiSelector.w,
                    this.multiSelector.h
                );
                this.ctxTop.restore();
            }
        },

        getGridSpacing() {
            let scale = (1 / Matrix.getScaling(this.matrix).x) * 30,
                spacing = 10; // minimum value 1 cm
            // console.warn('-----> 1 ', Matrix.getScaling(this.matrix), scale, spacing);
            let i = 1;
            while (Math.ceil(scale / spacing) > 1) {
                spacing *= i % 2 ? 5 : 2;
                i++;
            }
            // console.warn('-----> 2 ', Matrix.getScaling(this.matrix), scale, spacing);
            return spacing;
        },
        updateTransform() {
            this.refresh();
        },
        refreshGrid(ctx) {
            let p = new Point(this.currentMap.originX, this.currentMap.originY);

            if (ctx) ctx.clearRect(0, 0, this.currentMap.width, this.currentMap.height);
            if (ctx) ctx.save();

            this.drawCentralPoint(ctx, p);
            if (this.rulerMode === 1) {
                this.drawGridBackground(ctx, p, this.currentMap.grid.width);
            } else if (this.rulerMode === 2) {
                this.drawGrid(ctx, this.currentMap.grid.width, true);
                this.drawGrid(ctx, this.currentMap.subGrid.width);
            }

            if (ctx) ctx.restore();
        },
        refreshTop(ctx) {
            if (ctx) ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            // if (this.showPanic) Painter.drawPanic(ctx, 50 * this.ratio, 50 * this.ratio, 12 * this.ratio);
            if (this.showCompass)
                Painter.drawCompass(
                    ctx,
                    this.$refs.canvas.width - 60 * this.ratio,
                    75 * this.ratio,
                    60,
                    this.delta,
                    this.ratio
                );
            let p = new Point(this.currentMap.originX, this.currentMap.originY);
            // if (this.rulerMode === 1 && !this.disableRuler)
            //     this.drawRulers(ctx, RULER_TYPE.ALL, p, this.currentMap.grid.width);
        }, 
        drawCentralPoint(ctx, c) {
            let w = 2.5 * this.ratio;
            ctx.save();
            if (this.isometric) {
                // ctx.rotate(30 * (Math.PI / 180));
                c = Point.getIsometricPos(c.x, c.y);
            }
            Painter.drawCentralPoint(ctx, c.x, c.y, w);
            ctx.stroke();
            // Painter.drawCentralLine(ctx, c.x, c.y, this.$refs.canvas.width, this.$refs.canvas.height);
            ctx.restore();
        },
        drawRulers(ctx, rulerType, center, w) {
            if (rulerType === RULER_TYPE.HORIZONTAL) {
                this.drawHorizontalAxis(ctx, center, w);
            } else if (rulerType === RULER_TYPE.VERTICAL) {
                this.drawVerticalAxis(ctx, center, w);
            } else if (rulerType === RULER_TYPE.ALL) {
                this.drawHorizontalAxis(ctx, center, w);
                this.drawVerticalAxis(ctx, center, w);
            }
            // console.error(JSON.stringify(this.currentMap.pointer));
            // 计算mm毫米 m米的切换 2020.05.07
            let tcp = Point.getInstanceByAnotherMatrix(this.matrix, this.currentMap.pointer);
            this.updateXYInfo(tcp);
            if (!this.currentMap.pointer || this.currentMap.pointer.x === undefined) return;
            // pointer text
            ctx.save();
            // let h = 25 * this.ratio;
            // Painter.drawRulers(ctx, this.currentMap.pointer, h, this.ratio);
            ctx.restore();
        },
        drawHorizontalAxis(ctx, center, w) {
            let h = 25 * this.ratio,
                map = this.currentMap,
                pointer = this.currentMap.pointer;
            ctx.save();
            Painter.drawHorizontalAxis(ctx, this.matrix, map, center, pointer, w, h, this.ratio);
            ctx.restore();
        },
        drawVerticalAxis(ctx, center, w) {
            let h = 25 * this.ratio,
                map = this.currentMap,
                pointer = this.currentMap.pointer;
            ctx.save();
            Painter.drawVerticalAxis(ctx, this.matrix, map, center, pointer, w, h, this.ratio);
            ctx.restore();
        },
        drawGridBackground(ctx, center, w) {
            let ci = Math.floor(center.x / w),
                columnIndex = 0 - ci,
                ri = Math.floor(center.y / w),
                rowIndex = 0 - ri;
            ctx.save();
            ctx.beginPath();
            for (let cidx = columnIndex; center.x + cidx * w < this.currentMap.width; cidx++) {
                let x = Math.round(center.x + cidx * w);
                for (let ridx = rowIndex; center.y + ridx * w < this.currentMap.height; ridx++) {
                    let y = Math.round(center.y + ridx * w);
                    if ((cidx + ridx) % 2) {
                        ctx.rect(x, y, w, w);
                    }
                }
            }
            ctx.closePath();
            ctx.fillStyle = 'rgba(193, 193, 193, 0.1)';
            ctx.fill();

            ctx.restore();
        },
        drawGrid(ctx, spacing, isBold = false) {
            ctx.save();
            // //背景
            // let gradient = ctx.createLinearGradient(0, 0, 0, 300); // 创建线性的渐变对象。
            // gradient.addColorStop(0, '#000000');
            // gradient.addColorStop(1, '#ffffff');
            // ctx.fillStyle = gradient;
            // ctx.fillRect = (0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            // 边框
            let grid_cols = parseInt(this.currentMap.width / spacing),
                grid_rows = parseInt(this.currentMap.height / spacing),
                cell_height = spacing,
                cell_width = spacing;
            ctx.lineWidth = 0.5;
            ctx.strokeStyle = `rgba(0, 0, 0, ${isBold ? '0.25' : '0.1'})`;
            // 结束边框
            // ctx.beginPath();
            // 竖线
            for (let col = 0; col <= grid_cols; col++) {
                let x = parseInt(col * cell_width) + 0.5 + this.currentMap.grid.offsetX;
                // let p0 = new Point(x, 0),
                //     p1 = new Point(x, this.currentMap.height);
                // if (this.isometric) {
                //     p0 = Point.getIsometricPos(p0.x, p0.y);
                //     p1 = Point.getIsometricPos(p1.x, p1.y);
                // }
                // ctx.moveTo(p0.x, p0.y);
                // ctx.lineTo(p1.x, p1.y);
                ctx.moveTo(x, 0);
                ctx.lineTo(x, this.currentMap.height);
            }
            // 横线
            for (let row = 0; row <= grid_rows; row++) {
                let y = parseInt(row * cell_height) + 0.5 + this.currentMap.grid.offsetY;
                // let p0 = new Point(0, y),
                //     p1 = new Point(this.currentMap.width, y);
                // if (this.isometric) {
                //     p0 = Point.getIsometricPos(p0.x, p0.y);
                //     p1 = Point.getIsometricPos(p1.x, p1.y);
                // }
                // ctx.moveTo(p0.x, p0.y);
                // ctx.lineTo(p1.x, p1.y);
                ctx.moveTo(0, y);
                ctx.lineTo(this.currentMap.width, y);
            }
            ctx.stroke();
        },
        storeMultiSelector(op, cp) {
            if (!this.multiSelectable || !op) return;
            let x = Math.min(op.x, cp.x),
                y = Math.min(op.y, cp.y),
                w = Math.abs(parseInt(cp.x - op.x)),
                h = Math.abs(parseInt(cp.y - op.y));
            this.multiSelector = { x, y, w, h };
        },

        /** hammer events */
        handleTap(e) {
            // console.log('v-canvas-grid.handleTap: ', e, e.preventDefault());
            if (typeof this.tapFunc === 'function') this.tapFunc(e.srcEvent);
        },
        handlePan(e) {
            if (!this.draggable) return;
            // handlePan: _.throttle(function(e) {
            let op = null,
                top = null;
            let cp = Point.getInstanceByMouseOffset(e.srcEvent, this.$refs.canvas, this.ratio),
                tcp = Point.getInstanceByAnotherMatrix(this.matrix, cp);
            if (!this.lastEvent) {
                // first time
                op = Point.getOriginPointByPanEvents(this.$refs.canvas, e, this.ratio);
                top = Point.getInstanceByAnotherMatrix(this.matrix, op);
            } else {
                // next time
                op = Point.getInstanceByMouseOffset(this.lastEvent.srcEvent, this.$refs.canvas, this.ratio);
                top = Point.getInstanceByAnotherMatrix(this.matrix, op);
            }
            let dx = tcp.x - top.x,
                dy = tcp.y - top.y;
            // if (this.isometric) cp = Point.getPosFromIsometric(cp.x, cp.y);
            this.lastEvent = e;
            if (e.isFinal) this.lastEvent = null;
            // console.warn('v-canvas-grid.handlePan:  ', e.isFirst, e.isFinal, op, top, ', ', dx, dy);
            if (typeof this.panFunc === 'function') this.panFunc(e.srcEvent);
            this.updateXYInfo(cp);

            this.matrix = this.matrix.translate(dx, dy);
            // }, 17),
        },
        handlePinch(e) {
            // console.warn('v-canvas-grid.handlePinch:  ', e);
            let cp = Point.getInstanceByMouseOffset(e.srcEvent, this.$refs.canvas, this.ratio);
            let delta = e.scale > 1 ? 0.02 : -0.02;
            // console.warn('v-canvas-grid.handlePinch:  ', delta, cp.x, cp.y);
            this.zoom(delta, cp.x, cp.y);
        },

        /** events handler */
        clickHandler(e) {
            e.stopPropagation();
            e.preventDefault();
            // console.log('v-canvas-grid.clickHandler: ', e);
            // this.pointerEvents.push(e);
            if (typeof this.clickFunc === 'function') {
                this.clickFunc(e);
            }
        },
        pointerDownHandler(e) {
            // console.log('v-canvas-grid.pointerDownHandler: ', e, e.pointerId, e.type, e.button);
            // e.stopPropagation();
            // e.preventDefault();
            if (typeof e.pointerId === 'undefined' && e.type.indexOf('touch') < 0) return;
            this.startPoint = Point.getInstanceByMouseOffset(e, this.$refs.canvas, this.ratio);
            this.pointerButton = e.button;
            this.pointerEvents = [];
            // console.log('v-canvas-grid.pointerDownHandler: ', e, e.pointerId, this.pointerEvents.length, e.button);
            this.pointerEvents.push(e);
            if (typeof this.pointerDownFunc === 'function') {
                this.pointerDownFunc(e);
            }
        },
        pointerUpHandler(e) {
            // e.stopPropagation();
            // e.preventDefault();
            // console.log('v-canvas-grid.pointerUpHandler: ', e, e.pointerId);
            this.startPoint = null;
            this.multiSelector = null;
            this.pointerButton = -1;
            this.removeEvent(e);
            // if (this.tool) this.tool.pointerUpHandler();
            if (typeof this.pointerUpFunc === 'function') this.pointerUpFunc(e);
        },
        pointerLeaveHandler(e) {
            // console.log('v-canvas-grid.pointerLeaveHandler: ', e.pointerId);
            // e.stopPropagation();
            // e.preventDefault();
            this.startPoint = null;
            this.multiSelector = null;
            this.pointerButton = -1;
            this.removeEvent(e); // onmouseout would trigger for children too
            this.updateXYInfo(null);
            // if (this.tool) this.tool.pointerLeaveHandler();
            if (typeof this.pointerLeaveFunc === 'function') this.pointerLeaveFunc(e);
        },
        // pointerMoveHandler(e) {
        pointerMoveHandler: _.throttle(function(e) {
            // e.stopPropagation();
            // e.preventDefault();
            // pointerMoveHandler (e) {
            // console.log('v-canvas-grid.pointerMoveHandler: ', e.button);
            let cp = Point.getInstanceByMouseOffset(e, this.$refs.canvas, this.ratio);
            if (this.isometric) cp = Point.getPosFromIsometric(cp.x, cp.y);
            let tcp = Point.getInstanceByAnotherMatrix(this.matrix, cp);
            // console.warn('v-canvas-grid.pointerMoveHandler: ', cp, tcp);
            if (cp) {
                this.currentMap.pointer.x = cp.x;
                this.currentMap.pointer.y = cp.y;
            }
            // drag move with first pointer
            // if (this.pointerEvents.length === 1 && this.pointerEvents[0].pointerId === e.pointerId) {
            let touching =
                this.pointerEvents.length &&
                this.pointerEvents[0].type.indexOf('touch') >= 0 &&
                e.type.indexOf('touch') >= 0;
            if (this.pointerEvents.length === 1 && (this.pointerEvents[0].pointerId === e.pointerId || touching)) {
                // if (!this.tool || !this.tool.pointerMoveHandler(cp, tcp)) this.move(tcp);
                // if (!this.draggable && this.multiSelectable) this.storeMultiSelector(this.startPoint, cp);
                if (this.pointerButton !== 2 && this.multiSelectable) this.storeMultiSelector(this.startPoint, cp);
                if (!this.draggable && typeof this.pointerMoveFunc === 'function') this.pointerMoveFunc(e);
                else if (this.draggable) this.move(e, tcp);
            }
            if (this.pointerEvents.length === 2) this.pinchZoom(cp, e);
            if (this.pointerEvents.length) this.updateEvent(e);
            if (!this.pointerEvents.length || this.tool) this.updateXYInfo(tcp);
            // },
            // if (this.mouseDownFlag) bus.emit('p-sitemap.refresh');
            // },
            if (typeof this.pointerMoveFunc === 'function') this.pointerMoveFunc(e);
        }, 20),
        // mouseWheelHandler(e) {
        mouseWheelHandler: _.throttle(function(e) {
            if (!this.scalingable) return;
            // console.error(e.detail, e.wheelDelta);
            // e.preventDefault();
            let delta = e.detail < 0 || e.wheelDelta > 0 ? 0.1 : -0.1;
            let cp = Point.getInstanceByMouseOffset(e, this.$refs.canvas, this.ratio);
            if (this.isometric) cp = Point.getPosFromIsometric(cp.x, cp.y);
            this.zoom(delta, cp.x, cp.y);
            // 计算mm毫米 m米的切换 2020.05.07
            let tcp = Point.getInstanceByAnotherMatrix(this.matrix, cp);
            this.updateXYInfo(tcp);
            // bus.emit('p-sitemap.refresh');
            // },
        }, 50),

        updateXYInfo(point) {
            if (!point && this.currentMap.pointer) {
                this.currentMap.pointer.x = 0;
                this.currentMap.pointer.y = 0;
                return;
            }
            // console.log(
            //     '->',
            //     Math.round(point.x).toLocaleString('en-us'),
            //     Math.round(point.y).toLocaleString('en-us'),
            //     this.matrix.transformPoint().x,
            //     this.matrix.transformPoint().y
            // );
            if (this.currentMap.grid.spacingUnit === 'mm') {
                this.currentMap.pointer.mx = Math.round(point.x).toLocaleString('en-us');
                this.currentMap.pointer.my = Math.round(point.y).toLocaleString('en-us');
                this.currentMap.pointer.unit = 'mm';
            } else {
                this.currentMap.pointer.mx = Math.round(point.x / 1000).toLocaleString('en-us');
                this.currentMap.pointer.my = Math.round(point.y / 1000).toLocaleString('en-us');
                this.currentMap.pointer.unit = 'm';
            }
        },
        updateEvent(e) {
            for (let i = 0; i < this.pointerEvents.length; i++) {
                if (e.pointerId >= 0 && this.pointerEvents[i].pointerId === e.pointerId)
                    return (this.pointerEvents[i] = e);
                else if (this.pointerEvents[i].type.indexOf('touch') > -1) return (this.pointerEvents[i] = e);
            }
        },
        removeEvent(e) {
            for (let i = 0, len = this.pointerEvents.length; i < len; i++) {
                if (e.pointerId >= 0 && this.pointerEvents[i].pointerId === e.pointerId)
                    return this.pointerEvents.splice(i, 1);
                else if (this.pointerEvents[i].type.indexOf('touch') > -1) return this.pointerEvents.splice(i, 1);
            }
        },

        checkRangeOfCoordinate(e) {
            //eslint-disable-line
            let rtn = { x: true, y: true };
            if (!this.coordinateScope || !this.coordinateScope.length) return rtn;
            let p0 = new Point(0, 0),
                p1 = new Point(this.$refs.canvas.width, this.$refs.canvas.height),
                tp0 = Point.getInstanceByAnotherMatrix(this.matrix, p0),
                tp1 = Point.getInstanceByAnotherMatrix(this.matrix, p1);
            let dirc = 0; // 0: up, 1: right, 2: down, 3: left
            dirc = new Point(this.pointerEvents[0].clientX, this.pointerEvents[0].clientY).direction({
                x: e.clientX,
                y: e.clientY
            });
            if (dirc === 0 && tp1.y < this.coordinateScope[2]) rtn.y = false;
            else if (dirc === 1 && tp0.x < this.coordinateScope[0]) rtn.x = false;
            else if (dirc === 2 && tp0.y > this.coordinateScope[3]) rtn.y = false;
            else if (dirc === 3 && tp1.x > this.coordinateScope[1]) rtn.x = false;

            // if (dirc === 0) console.log(dirc, tp1.y, this.coordinateScope[2], rtn);

            return rtn;
        },
        checkRangeOfScaling(delta) {
            //eslint-disable-line
            let rtn = true;
            if (!this.scalingScope || !this.scalingScope.length) return rtn;
            if (delta > 0 && this.currentMap.scaling >= this.scalingScope[1]) rtn = false;
            if (delta < 0 && this.currentMap.scaling <= this.scalingScope[0]) rtn = false;
            // console.log('v-canvas-grid.checkRangeOfScaling: ', delta, this.currentMap.scaling, this.scalingScope, rtn);
            return rtn;
        },

        move(e, tcp) {
            if (!tcp || tcp.x === undefined) return;
            if (this.pointerButton !== 2) return;

            let op = Point.getInstanceByMouseOffset(this.pointerEvents[0], this.$refs.canvas, this.ratio);
            if (this.isometric) op = Point.getPosFromIsometric(op.x, op.y);
            let top = Point.getInstanceByAnotherMatrix(this.matrix, op);
            let dx = tcp.x - top.x,
                dy = tcp.y - top.y;

            let off = this.checkRangeOfCoordinate(e);
            if (!off.x) dx = 0;
            if (!off.y) dy = 0;
            // console.log('v-canvas-grid.move: ', op, top, tcp, dx, dy);
            // console.log('v-canvas-grid.move: ', off, dx, dy);
            this.matrix = this.matrix.translate(dx, dy);
            this.updateTransform();
        },
        pinchZoom(cp, e) {
            let p0 = Point.getInstanceByMouseOffset(this.pointerEvents[0], this.$refs.canvas, this.ratio),
                p1 = Point.getInstanceByMouseOffset(this.pointerEvents[1], this.$refs.canvas, this.ratio);

            if (this.isometric) {
                p0 = Point.getPosFromIsometric(p0.x, p0.y);
                p1 = Point.getPosFromIsometric(p1.x, p1.y);
            }

            let pOld = this.pointerEvents[0].pointerId === e.pointerId ? p0 : p1,
                pFix = this.pointerEvents[1].pointerId === e.pointerId ? p0 : p1;

            let delta = cp.distance(pFix) / pOld.distance(pFix) - 1;
            if (delta === Infinity || delta > 0) delta = 0.03;
            else delta = -0.03;
            // console.log('v-canvas-grid.pinchZoom: ', cp.distance(pFix), pOld.distance(pFix), delta);
            this.zoom(delta, pFix.x, pFix.y);
        },
        zoom(delta, x, y) {
            // delta value will approach 10x magnification
            // console.log('v-canvas-grid.zoom: -> ', delta);
            delta = Math.min(5 / Matrix.getScaling(this.matrix).x - 0.5, delta);
            // console.log('v-canvas-grid.zoom: ', delta, this.map.scaling);

            let crs = this.checkRangeOfScaling(delta);
            if (!crs) delta = 0;

            let focus = Point.getInstanceByAnotherMatrix(this.matrix, new Point(x, y));

            this.matrix = this.matrix.translate(focus.x, focus.y);
            this.matrix = this.matrix.scale(1 + delta);
            this.matrix = this.matrix.translate(-focus.x, -focus.y);

            this.updateTransform();
            // this.updateDrawMarkerScale();
        },
        zoomIn() {
            let w = this.$refs.canvas.clientWidth,
                h = this.$refs.canvas.clientHeight;

            this.zoom(0.2, w / 2, h / 2);
        },
        zoomOut() {
            let w = this.$refs.canvas.clientWidth,
                h = this.$refs.canvas.clientHeight;

            this.zoom(-0.2, w / 2, h / 2);
        },
        rotate(delta, x, y) {
            if (!x) x = this.$refs.canvas.clientWidth / 2;
            if (!y) y = this.$refs.canvas.clientHeight / 2;
            let focus = Point.getInstanceByAnotherMatrix(this.matrix, new Point(x, y));

            /**
             * 如果旋转角度为0，指南针不变化
             * @author sunleqing
             * @date 2020.03.31
             */
            if (delta === 0) this.delta = 0;
            else this.delta += delta;
            this.matrix = this.matrix.translate(focus.x, focus.y);
            this.matrix = this.matrix.rotate(delta);
            // this.matrix = this.matrix.rotate(Math.round(delta * (Math.PI / 180)));
            this.matrix = this.matrix.translate(-focus.x, -focus.y);
            this.updateTransform();
            // this.updateLabelRotation();
        },

        // Cache for offscrren render.
        setCtxCache(key, canvas) {
            if (!this.ctxCache) this.ctxCache = { key: '', val: null };
            if (this.ctxCache.key !== key) {
                this.ctxCache.key = key;
                this.ctxCache.val = canvas;
            }
            return this.ctxCache.val;
        },
        getCtxCache(key) {
            if (this.ctxCache && this.ctxCache.key === key) return this.ctxCache.val;
            return null;
        },
        setCtxTopCache(key, canvas) {
            if (!this.ctxTopCache) this.ctxTopCache = { key: '', val: null };
            if (this.ctxTopCache.key !== key) {
                this.ctxTopCache.key = key;
                this.ctxTopCache.val = canvas;
            }
            return this.ctxTopCache.val;
        },
        getCtxTopCache(key) {
            if (this.ctxTopCache && this.ctxTopCache.key === key) return this.ctxTopCache.val;
            return null;
        }
    }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<!-- <style scoped lang="scss"> -->
<style lang="scss">
.v-canvas-grid {
    width: 100%;
    height: 100%;
    position: relative;
    // cursor: pointer;

    .canvas,
    .canvasTop {
        width: 100%;
        height: 100%;
        position: absolute;
        top: 0;
        left: 0;
        /*z-index: 1;*/
    }
}
</style>
